package core.evolutionary;

import java.util.Random;
import java.util.Set;

import core.Problem;

public class ElemasAgent extends Agent {
	 private int whenToBecomeElitist;
	 private int transferableElitarityQuantity;
	 private double densityVerificationDistance;
	 private int initialElitarityLevel;
	 private int meetingsCounter;
	 /**
	 * licznik, ile razy agent dominowal pozostale
	 */
	private int dominatingCounter;
	 private int closeMeetings;
	/**
	 * licznik, ile razy agent byl zdominowany przez pozostale
	 */
	private int dominatedCounter;
	private int othersCloseMeetings;
	 
	 public ElemasAgent(Island island, Problem problem, double[] genotype,
				int initialLifeEnergyLevel, int transferableLifeEnergyQuantity,
				int whenToDie, int whenToReproduce, double actionsProbability,
				double mutationParameter, Random generator, int whenToBecomeElitist,
				int transferableElitarityQuantity, double densityVerificationDistance, int initialElitarityLevel) {
		 super(island, problem, genotype, initialLifeEnergyLevel, transferableLifeEnergyQuantity, whenToDie, whenToReproduce, actionsProbability, mutationParameter, generator);
		 this.whenToBecomeElitist = whenToBecomeElitist;
		 this.transferableElitarityQuantity = transferableElitarityQuantity;
		 this.densityVerificationDistance = densityVerificationDistance;
		 this.initialElitarityLevel = initialElitarityLevel;
		 this.meetingsCounter = 0;
		 this.dominatingCounter = 0;
		 this.dominatedCounter = 0;
		 this.closeMeetings = 0;
		 this.resources = new int[2];
		 //this.resources[0] = initialLifeEnergyLevel;
		 this.resources[1] = initialElitarityLevel;
	 }
	 
	 public void lifeStep() {
		 int setSize;
		 int randomValue;
		 if(this.island.isElitist()) {
			 //spotkanie

			Set<Agent> neighbours;
			setSize = (neighbours = this.island.getNeighbours(this)).size();
			if (generator.nextDouble() < this.actionsProbability && setSize != 0) {
				randomValue = generator.nextInt(setSize);
				Agent agent = null;
				for (Agent element : neighbours) {
					if (randomValue-- <= 0) {
						agent = element;
						break;
					}
				}
				double hisResults[] = agent.getResults();
				try {
					if (problem.isFirstDominatingTheOther(this.getResults(),
							hisResults)) {
						agent.island.giveResources(0, this.resources[0]);
						agent.island.remove(this);
						agent.getEnvironment().remove(this);
					} else if (problem.isFirstDominatingTheOther(hisResults, this
							.getResults())) {
						this.island.giveResources(0, this.resources[0]);
						this.island.remove(this);
						this.getEnvironment().remove(this);
					}
				} catch (IndexOutOfBoundsException e) {
					e.printStackTrace();
				}
			}
		 } else {
			// smierc
			if (this.resources[0] <= this.whenToDie) {
				this.island.giveResources(0, this.resources[0]);
				this.island.remove(this);
				this.getEnvironment().remove(this);
				return;
			}
			//migracja
			Set<Edge> edges;

			if (generator.nextDouble() < this.actionsProbability / 2
					&& (setSize = (edges = this.island.getMigratableEdges()).size()) != 0) {
				randomValue = generator.nextInt(setSize);
				Edge edge = null;
				for (Edge element : edges) {
					if (randomValue-- <= 0) {
						edge = element;
						break;
					}
				}
				int cost;
				if (this.resources[0] > (cost = edge.getCost()) + this.whenToDie) {
					this.island.giveResources(0, this.takeResource(0, cost));
					this.island.remove(this);
					this.island = edge.getToIsland();
					this.island.add(this);
				}
			}

			//spotkanie
			Set<Agent> neighbours;
			setSize = (neighbours = this.island.getNeighbours(this)).size();
			
			if (generator.nextDouble() < this.actionsProbability && setSize != 0) {
				randomValue = generator.nextInt(setSize);
				ElemasAgent agent = null;
				for (Agent element : neighbours) {
					if (randomValue-- <= 0) {
						agent = (ElemasAgent) element;
						break;
					}
				}
				double hisResults[] = agent.getResults();
				this.meetingsCounter += 1;
				agent.meetingsCounter += 1;
				try {
					if (problem.isFirstDominatingTheOther(this.getResults(),
							hisResults)) {
//						System.out.println("a ("+this.results[0]+";"+this.results[1]+"),("+hisResults[0]+";"+hisResults[1]+")");
						agent.transferResourceTo(this, 0, transferableLifeEnergyQuantity);
						this.dominatingCounter += 1;
						agent.dominatedCounter += 1;
						this.resources[1] += this.transferableElitarityQuantity;
					} else if (problem.isFirstDominatingTheOther(hisResults, this.getResults())) {
//						System.out.println("b ("+this.results[0]+";"+this.results[1]+"),("+hisResults[0]+";"+hisResults[1]+")");
						agent.dominatingCounter += 1;
						this.dominatedCounter += 1;
						this.transferResourceTo(agent, 0, transferableLifeEnergyQuantity);
						agent.resources[1] += agent.transferableElitarityQuantity;
					} else {
						double agentsDominatedLevel = agent.getDominatedLevel();
						double thissDominatedLevel = this.getDominatedLevel();
						if (thissDominatedLevel<agentsDominatedLevel) {
							agent.transferResourceTo(this, 0, transferableLifeEnergyQuantity);
							//this.resources[1] += this.transferableElitarityQuantity;
							
						} else if(thissDominatedLevel>agentsDominatedLevel) {
							this.transferResourceTo(agent, 0, transferableLifeEnergyQuantity);
							//agent.resources[1] += agent.transferableElitarityQuantity;
							
						} else {
							double agentsLevelOfDensity = agent.getDensityLevel();
							double thissLevelOfDensity = this.getDensityLevel();
							if(thissLevelOfDensity<agentsLevelOfDensity && (agent.resources[0]>agent.transferableLifeEnergyQuantity || agent.getDominatedCounter()>0.0) ) {
								agent.transferResourceTo(this, 0, transferableLifeEnergyQuantity);
								//this.resources[1] += this.transferableElitarityQuantity;
								
							} else if(thissLevelOfDensity>agentsLevelOfDensity && (this.resources[0]>this.transferableLifeEnergyQuantity || this.getDominatedCounter()>0.0) ) {
								this.transferResourceTo(agent, 0, transferableLifeEnergyQuantity);
								//agent.resources[1] += agent.transferableElitarityQuantity;
								
							} 
						}
					}
				} catch (IndexOutOfBoundsException e) {
					e.printStackTrace();
				}
				double distance = this.getDistanceFrom(agent);
				
				if(distance < this.densityVerificationDistance) {
					agent.closeMeetings += 1;
					this.closeMeetings += 1;
				}
				
				this.othersCloseMeetings += agent.closeMeetings;
				agent.othersCloseMeetings += this.closeMeetings;
				

			}
			
			//reprodukcja
			if (generator.nextDouble() < this.actionsProbability && (setSize != 0) && this.wantsToReproduce()) {
				randomValue = generator.nextInt(setSize);
				ElemasAgent agent = null;
				for (Agent element : neighbours) {
					if (randomValue-- <= 0) {
						agent = (ElemasAgent) element;
						break;
					}
				}

				if (agent.wantsToReproduce()) {
					double[] hisGenotype = agent.getGenotype();
					int length = this.genotype.length;
					double[] genotype1 = new double[length];
					double[] genotype2 = new double[length];
//					System.out.println("Repro1: "+ this.resources[0] + " " + agent.resources[0]);
					length--;
					int i = 0;
					double proportion;
					try {
						while (true) {
							proportion = (i + 0.0) / length;
							genotype1[i] = this.genotype[i] * proportion
									+ hisGenotype[i] * (1 - proportion);
							genotype2[i] = hisGenotype[i] * proportion
									+ this.genotype[i] * (1 - proportion);
							i++;
						}
					} catch (ArrayIndexOutOfBoundsException e) {
						// tu nic nie robimy
					}

					this.mutate(genotype1);
					this.mutate(genotype2);
					problem.normalize(genotype1);
					problem.normalize(genotype2);
					
					ElemasAgent a1 = new ElemasAgent(
							island,
							problem,
							genotype1,
							(this.initialLifeEnergyLevel + agent
									.getInitialLifeEnergyLevel()) / 2,
							(this.transferableLifeEnergyQuantity + agent
									.getTransferableLifeEnergyQuantity()) / 2,
							(this.whenToDie + agent.getWhenToDie()) / 2,
							(this.whenToReproduce + agent.getWhenToReproduce()) / 2,
							this.actionsProbability, this.mutationParameter, this.generator, this.whenToBecomeElitist, this.transferableElitarityQuantity, this.densityVerificationDistance, this.initialElitarityLevel);
					ElemasAgent a2 = new ElemasAgent(
							island,
							problem,
							genotype1,
							(this.initialLifeEnergyLevel + agent
									.getInitialLifeEnergyLevel()) / 2,
							(this.transferableLifeEnergyQuantity + agent
									.getTransferableLifeEnergyQuantity()) / 2,
							(this.whenToDie + agent.getWhenToDie()) / 2,
							(this.whenToReproduce + agent.getWhenToReproduce()) / 2,
							this.actionsProbability, this.mutationParameter, this.generator, this.whenToBecomeElitist, this.transferableElitarityQuantity, this.densityVerificationDistance, this.initialElitarityLevel);
					
					try {
						if(problem.isFirstDominatingTheOther(this.getResults(), agent.getResults())) {
//							System.out.println("a1");
							int tmp = (agent.resources[0] - agent.getTransferableLifeEnergyQuantity())/2;
							agent.transferResourceTo(a1, 0, tmp);
							this.transferResourceTo(a1, 0, this.getInitialLifeEnergyLevel()-tmp);
							agent.transferResourceTo(a2, 0, tmp);
							this.transferResourceTo(a2, 0, this.getInitialLifeEnergyLevel()-tmp);
						} else if (problem.isFirstDominatingTheOther(agent.getResults(), this.getResults())) {
//							System.out.println("a2");
							int tmp = (this.resources[0] - this.getTransferableLifeEnergyQuantity())/2;
							this.transferResourceTo(a1, 0, tmp);
							agent.transferResourceTo(a1, 0, agent.getInitialLifeEnergyLevel()-tmp);
							this.transferResourceTo(a2, 0, tmp);
							agent.transferResourceTo(a2, 0, agent.getInitialLifeEnergyLevel()-tmp);
							
						} else {
							if(this.dominatingCounter<=agent.dominatingCounter) {
								if(this.dominatedCounter>0) {
//									System.out.println("b1");
									int tmp = (this.resources[0] - 2*this.getTransferableLifeEnergyQuantity())/2;
									this.transferResourceTo(a1, 0, tmp);
									agent.transferResourceTo(a1, 0, agent.getInitialLifeEnergyLevel()-tmp);
									this.transferResourceTo(a2, 0, tmp);
									agent.transferResourceTo(a2, 0, agent.getInitialLifeEnergyLevel()-tmp);
									
								} else {
//									System.out.println("b2");
									int tmp = (agent.resources[0] - 2*agent.getTransferableLifeEnergyQuantity())/2;
									agent.transferResourceTo(a1, 0, tmp);
									this.transferResourceTo(a1, 0, this.getInitialLifeEnergyLevel()-tmp);
									agent.transferResourceTo(a2, 0, tmp);
									this.transferResourceTo(a2, 0, this.getInitialLifeEnergyLevel()-tmp);
									
								}
							} else {
								if(agent.dominatedCounter>0) {
//									System.out.println("c1");
									int tmp = (agent.resources[0] - 2*agent.getTransferableLifeEnergyQuantity())/2;
									agent.transferResourceTo(a1, 0, tmp);
									this.transferResourceTo(a1, 0, this.getInitialLifeEnergyLevel()-tmp);
									agent.transferResourceTo(a2, 0, tmp);
									this.transferResourceTo(a2, 0, this.getInitialLifeEnergyLevel()-tmp);
									
								} else {
//									System.out.println("c2");
									int tmp = (this.resources[0] - 2*this.getTransferableLifeEnergyQuantity())/2;
									this.transferResourceTo(a1, 0, tmp);
									agent.transferResourceTo(a1, 0, agent.getInitialLifeEnergyLevel()-tmp);
									this.transferResourceTo(a2, 0, tmp);
									agent.transferResourceTo(a2, 0, agent.getInitialLifeEnergyLevel()-tmp);
									
								}
								
							}
							//tutaj Strategies.py 182
						}
					} catch (IndexOutOfBoundsException e) {
						// Do nothing
						e.printStackTrace();
					}
					
//					System.out.println("Repro2: "+ this.resources[0] + " " + agent.resources[0]);
					a1.lifeStep();
					a2.lifeStep();
					
				}
			}
			
			//elitaryzacja
			if (generator.nextDouble() < this.actionsProbability && this.resources[1]>=this.whenToBecomeElitist && this.closeMeetings>(this.othersCloseMeetings+0.0)/this.meetingsCounter && (setSize = (edges = this.island.getMigratableEdges(true)).size()) != 0) {
				randomValue = generator.nextInt(setSize);
				Edge edge = null;
				for (Edge element : edges) {
					if (randomValue-- <= 0) {
						edge = element;
						break;
					}
				}
				int cost;
				if (this.resources[0] > (cost = edge.getCost()) + this.whenToDie) {
					this.island.giveResources(0, this.takeResource(0, cost));
					this.island.remove(this);
					this.island = edge.getToIsland();
					this.island.add(this);
				}
				
			}
			
			
		 }
	 }

	private double getDominatedCounter() {
		return this.dominatedCounter;
	}

	private double getDominatedLevel() {
		return (this.meetingsCounter==0)?0:this.dominatedCounter/this.meetingsCounter;
	}

	private double getDensityLevel() {
		return this.closeMeetings/((this.meetingsCounter==0)?1.0:this.meetingsCounter+0.0);
	}

	private double getDistanceFrom(ElemasAgent agent) {
		int i=0;
		double sum=0.0;
		try {
			while(true) {
				sum+=Math.pow(this.genotype[i]-agent.genotype[i], 2.0);
				i++;
			}
		} catch (IndexOutOfBoundsException e) {
		}
		return Math.sqrt(sum);
	}
	 
	 
}
